//
// Created by Lenovo on 2023/3/9.
//

/* 函数栈帧的创建和销毁 */

#include <stdio.h>

int Add(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}

int main()
{
    // 1.寄存器
    // eax, ebx, ecx, edx     ebp, esp
    // ebp esp 这两个寄存器中存放的是地址，这两个地址是用来维护函数栈帧的
    //每一个函数调用，都要在栈区创建一个空间
    //假设栈区中地址由低到高，中间开辟了一块空间存储 main 函数，这一块区域就被称作 main 函数的函数栈帧
    //其中 esp(栈顶指针) 存放栈帧低地址，ebp(栈底指针) 存放栈帧高地址
    //栈区先使用高地址，再使用低地址，先放进去的数据在栈底

    int a = 10;
    int b = 20;
    int c = 0;

    c = Add(a, b);

    printf("%d\n", c);

    //在 VS2013 中，main 函数也是被别人调用的
    // mainCRTStartup --> __tmainCRTStartup --> main

    //以反汇编语言执行 main 函数(VS2013)
    //在开始时，ebp 是 __tmainCRTStartup 的栈底指针，esp 是它的栈顶指针
    // ebp:0x008ffbf4     esp:0x008ffba8

    /*
     * int main()
     * {
     * 00C21410  push        ebp                        进行压栈操作，把 ebp 压入栈顶，此时 esp 的指针会上移 4，变成 0x008ffba4
     * 00C21411  mov         ebp,esp                    把 esp 中的值赋给 ebp，ebp 变为 0x008ffba4
     * 00C21413  sub         esp,0E4h                   把 esp 减去 0E4h(0x000000e4)，即 esp 上浮，此时 esp 变为 0x008ffac0
     * ...                                              此时 ebp 和 esp 有了自己新维护的空间(0x008ffba4 ~ 0x008ffac0)，即为 main 函数预开辟的函数栈帧
     * 00C21419  push        ebx
     * 00C2141A  push        esi
     * 00C2141B  push        edi                        向栈顶压入三个值，esp 上移至 0x008ffab4
     * 00C2141C  lea         edi,[ebp-0E4h]             lea(load effective address)加载有效地址，把 edi 变为 ebp-0E4h(原栈顶 0x008ffac0)，此时 edi 和 esp 之间就是三个新压入栈中的值
     * 00C21422  mov         ecx,39h                    ecx 是计数器
     * 00C21427  mov         eax,0CCCCCCCCh
     * 00C2142C  rep stos    dword ptr es:[edi]         把从 edi 开始向下的 39h 次(?)数据全部改成 0xCCCCCCCC，其中 dword 是指 double word(双字节)，一个 word 是两个字节，两个 word 是四个字节
     * ...                                              那么一次就是四个字节，正好一个指针，上方四个操作实际上把 edi 到 ebp 之间(栈底-原栈顶)的数据全部初始化成 0xCCCCCCCC
     * ---------------------------------------          此时 main 函数的栈帧已经准备完毕
     *     int a = 10;
     * 00C2142E  mov         dword ptr [ebp-8],0Ah      把 0Ah 这个十六进制的数字(10) 放到 ebp-8 中，此时 ebp-8 ~ ebp-4 的空间被分配给 a，如果 a 没有被赋值，默认存储 CCCCCCCC(烫烫烫烫)，不同编译器默认存储的数据不同
     *     int b = 20;
     * 00C21435  mov         dword ptr [ebp-14h],14h    同理，VS2013 中的两个变量相距比较远(12字节)，可能别的编译器会有所不同
     *     int c = 0;
     * 00C2143C  mov         dword ptr [ebp-20h],0
     *     c = Add(a, b);
     * 00C21443  mov         eax,dword ptr [ebp-14h]    把 ebp-14h 的值(b 的值)放到 eax 中
     * 00C21446  push        eax                        把 eax(20) 压入栈顶，esp 上浮 4 个字节(0x008ffab0)
     * 00C21447  mov         ecx,dword ptr [ebp-8]      把 ebp-8 的值(a 的值)放到 ecx 中
     * 00C2144A  push        ecx                        把 ecx(10) 压入栈顶，esp 上浮 4 个字节(0x008ffaac)
     * ...                                              上面四步操作完成了函数传参的过程，从右向左传参
     * 00C2144B  call        00C210E1                   call 调用函数，同时把 call 指令的下一条指令的地址(00C21450)压入栈顶，esp 上浮 4 个字节(0x008ffaa8)
     * ...                                              下一次操作会跳转到函数指针的地址 0x00C210E1 并继续向下执行，在栈顶压入 call 的下一条指令的地址以便于函数执行结束后找到下一条指令的位置
     * ---------------------------------------          进入 Add 函数
     * int Add(int x, int y)
     * {
     * ...                                              开始为 Add 函数分配栈帧
     * 00C213C0  push        ebp                        把 ebp 的地址(0x008ffba4)压入栈顶，此时 ebp 还是 main 函数的栈底指针，esp 上浮 4 个字节(0x008ffaa4)
     * 00C213C1  mov         ebp,esp                    把 esp 赋值给 ebp
     * 00C213C3  sub         esp,0CCh                   esp 减去 0CCh，变为 0x008ff9d8
     * 00C213C9  push        ebx
     * 00C213CA  push        esi
     * 00C213CB  push        edi                        三次 push 压栈，esp 上浮到 0x008ff9cc
     * 00C213CC  lea         edi,[ebp+FFFFFF34h]        表面上看结果是 ebp 下沉，其实加完之后最高位溢出，结果正好是 0x008ff9d8
     * 00C213D2  mov         ecx,33h
     * 00C213D7  mov         eax,0CCCCCCCCh
     * 00C213DC  rep stos    dword ptr es:[edi]         同理，将栈帧内的空间全部初始化为 0xCCCCCCCC
     * ---------------------------------------          Add 函数栈帧创建完成
     *     int z = 0;
     * 00C213DE  mov         dword ptr [ebp-8],0        把 ebp-8 ~ ebp-4 分配给 z 并初始化成 0
     *     z = x + y;
     * 00C213E5  mov         eax,dword ptr [ebp+8]      ebp+8(0x008ffaac) 存放的是 a 的值，把 a 的值赋给 eax
     * 00C213E8  add         eax,dword ptr [ebp+0Ch]    ebp+0xC(0x008ffab0) 存放的是 b 的值，eax 加上 b 的值
     * 00C213EB  mov         dword ptr [ebp-8],eax      ebp-8 指向 z，把 eax 中的值赋给 z
     * ...                                              加法计算完成，由此可知形参并没有在 Add 内部创建空间，而是在函数创建之前先压入栈中，执行计算的时候再回去寻找两个形参，所以说形参是实参的一份临时拷贝
     *     return z;
     * 00C213EE  mov         eax,dword ptr [ebp-8]      把 ebp-8(z) 的值赋给 eax
     * }
     * 00C213F1  pop         edi                        pop 弹出栈顶元素，同时把栈顶元素赋值给 edi，esp 下沉 4 至 0x008ff9d0
     * 00C213F2  pop         esi
     * 00C213F3  pop         ebx                        esp 下沉至 0x008ff9d8
     * 00C213F4  mov         esp,ebp                    把 ebp 的值赋给 esp，此时 ebp = esp = 0x008ffaa4，esp 所在位置默认为栈顶，所以此时的 Add 函数栈帧已经不存在了
     * 00C213F6  pop         ebp                        弹出栈顶元素，并将其赋值给 ebp，而此时栈顶(0x008ffaa4)存储的是原 main 函数栈底指针的地址，所以 ebp 又变为 main 的栈底指针(0x008ffba4)
     * ...                                              同时，esp 下沉 4 至 0x008ffaa8
     * 00C213F7  ret                                    从栈顶弹出并转到 call 指令下一条指令的地址(0x008ffaa8 ~ 0x008ffaac)，并且 esp 下沉 4 至 0x008ffaac
     * ----------------------------------------         返回 main 函数(00C21450)
     * 00C21450  add         esp,8                      esp 下沉 8 至 0x008ffab4，释放了形参的空间
     * 00C21453  mov         dword ptr [ebp-20h],eax    ebp-20h 中存放的是 z，把函数的返回值(eax 中)赋给 z
     * ...
     * }
     */
    //函数预开辟的空间由编译器进行计算，每个函数开辟的大小不同

    return 0;
}